package station;

/**
 * Vorlesung: Verteilte Systeme
 * Aufgabe  : 04
 * 
 * @author Benjamin Burchard
 * @author Tell Mueller-Pettenpohl
 * 
 */

import static station.DataMessage.NEXT_SLOT_BYTE;
import helper.DatagramPrinter;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;

import timeDivisionMultipleAccess.Channel;
import timeDivisionMultipleAccess.TimeSyncer;
import timeDivisionMultipleAccess.exceptions.IllegalSlotException;

public class Receiver implements Runnable {

    private Thread receiverThread;
    private boolean isRunning;
    private MulticastSocket mcastSocket;
    private DatagramSocket udpSocket;
    private byte udpBuffer[] = new byte[33];
    private String mcastAddr;
    private String ifaceAddr;
    private int udpPortSink;
    private TimeSyncer syncer;
    private Channel channel;

    /**
     * Creates a new receiver for receiving Multicasts and sending UDP packages
     * to the Sink. It also synchronizes the {@link Station} to the frame.
     * 
     * @param ifaceAddr
     * @param mcastAddr
     * @param mcastPort
     * @param udpPortSink
     * @param syncer
     * @param channel
     * 
     * @throws UnknownHostException
     * @throws SocketException
     * @throws IOException
     */
    public Receiver(String ifaceAddr, String mcastAddr, int mcastPort,
            int udpPortSink, TimeSyncer syncer, Channel channel)
            throws UnknownHostException, SocketException, IOException {
        // Thread
        this.isRunning = true;
        this.receiverThread = new Thread(this);
        // UDP
        this.ifaceAddr = ifaceAddr;
        this.udpPortSink = udpPortSink;
        this.udpSocket = new DatagramSocket();
        // Multicast
        this.mcastAddr = mcastAddr;
        this.mcastSocket = new MulticastSocket(mcastPort);
        this.mcastSocket.joinGroup(InetAddress.getByName(mcastAddr));
        this.syncer = syncer;
        this.channel = channel;
    }

    /**
     * Start the Thread! See: {@link Thread#start()}
     */
    public void start() {
        this.receiverThread.start();
    }

    /**
     * Sets running = false and closes UDP & Multicast Sockets
     * 
     * @throws UnknownHostException
     * @throws IOException
     */
    public void terminate() throws UnknownHostException, IOException {
        mcastSocket.leaveGroup(InetAddress.getByName(mcastAddr));
        mcastSocket.close();
        udpSocket.close();
        this.isRunning = false;
    }

    @Override
    public void run() {
        try {
            // open channel to lock and unlock slots
            DatagramPacket packet = new DatagramPacket(udpBuffer,
                    udpBuffer.length);
            byte nextSlot;
            while (isRunning) {
                mcastSocket.receive(packet);
                long timestamp = DataMessage.getTime(packet.getData());
                nextSlot = packet.getData()[NEXT_SLOT_BYTE];
                syncer.syncTime(timestamp, syncer.getCurrentTime());
                channel.setActSlotTime(timestamp, nextSlot);

                System.out.println("Receiver received: " + packet.getData());
                DatagramPrinter.printDatagram(udpBuffer);
                try {
                    channel.lockSlot(nextSlot);
                } catch (IllegalSlotException e) {
                    e.printStackTrace();
                }
                
                /*
                 * TODO: Implement TimeStamp receiving & Synchronizing to Frame
                 * getTimestamp(); syncToFrame(); --> Sind wir vllt schon
                 * gesynct? --> lokaler boolean "ja wir sind gesyced" --> wenn
                 * ja dann nix machen
                 */

                // Send to sink
                udpSocket.send(new DatagramPacket(packet.getData(), packet
                        .getLength(), InetAddress.getByName(ifaceAddr),
                        udpPortSink));
            }
            terminate();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
